iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0

實作完登入的驗證信之後,我們還可以利用 Laravel 內建的用戶功能,來做忘記密碼的重設信件。

幸運的是!這一段 Laravel 已經幾乎全部幫你做完了!

不過我們不能只知其然,不知其所以然。對 Laravel 實際做了些什麼,我們還是要看過一輪的!

忘記密碼這個功能牽涉到資安,所以在開發過程要特別注意一些安全問題。

如果我們要在正式服務上允許密碼重設,那麼建議要把 app/Http/Kernel.php 裡面的

// \App\Http\Middleware\TrustHosts::class,

這段取消註解,並且在 .env

APP_URL=http://ithome-2022-example-app.test

填寫上正式的服務網址,以免 Laravel 不認得自己送過來的請求。

不過我們目前在本機上開發,就先不處理這段邏輯。

首先,是我們的 app/Models/User.php 要實作 Illuminate\Contracts\Auth\CanResetPassword 介面,以及加入 Illuminate\Auth\Passwords\CanResetPassword trait。

幸運的是, Laravel 內建的 Illuminate\Foundation\Auth\User 已經幫我們做完了

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}

接著,我們看忘記密碼畫面的路由,這段路由位在 web.php 裡面的

require __DIR__.'/auth.php';

然後,我們進到 auth.php 看看裡面的邏輯。主要先看的是這兩段

Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
			->name('password.request');

Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
			->name('password.email');

PasswordResetLinkController.create() 這段是用來生成忘記密碼畫面,其實作如下

public function create()
{
	return view('auth.forgot-password');
}

相信各位讀者已經看出來,這邊跟用戶認證時驗證 email 的邏輯一樣,都是用 route name 抓到行為。 另外 Laravel 很貼心的已經幫我們把 auth.forgot-password 的 view 做好了。大家可以在 resources/views/auth/forgot-password.blade.php 裡面看到

<x-guest-layout>
    <x-auth-card>
        <x-slot name="logo">
            <a href="/">
                <x-application-logo class="w-20 h-20 fill-current text-gray-500" />
            </a>
        </x-slot>

        <div class="mb-4 text-sm text-gray-600">
            {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
        </div>

        <!-- Session Status -->
        <x-auth-session-status class="mb-4" :status="session('status')" />

        <!-- Validation Errors -->
        <x-auth-validation-errors class="mb-4" :errors="$errors" />

        <form method="POST" action="{{ route('password.email') }}">
            @csrf

            <!-- Email Address -->
            <div>
                <x-input-label for="email" :value="__('Email')" />

                <x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
            </div>

            <div class="flex items-center justify-end mt-4">
                <x-primary-button>
                    {{ __('Email Password Reset Link') }}
                </x-primary-button>
            </div>
        </form>
    </x-auth-card>
</x-guest-layout>

這邊用到的是前面介紹過的 component 功能(忘記了嗎?快回去 Day 06:優化我們的網頁外觀,善用 component 減少撰寫的前端程式 看看!)

接著,我們看收到忘記密碼申請時的行為 PasswordResetLinkController.store()

public function store(Request $request)
{
	$request->validate([
		'email' => ['required', 'email'],
	]);

	// We will send the password reset link to this user. Once we have attempted
	// to send the link, we will examine the response then see the message we
	// need to show to the user. Finally, we'll send out a proper response.
	$status = Password::sendResetLink(
		$request->only('email')
	);

	return $status == Password::RESET_LINK_SENT
				? back()->with('status', __($status))
				: back()->withInput($request->only('email'))
						->withErrors(['email' => __($status)]);
}

這邊的邏輯比起產生畫面,要複雜一點點,不過還是很單純的。

簡單的說,如果我們收到了忘記密碼的請求,我們先確認對方輸入的 email 正確,接著就生成重設密碼的路徑,透過 email 給使用者。

接著就是重設密碼了,路由寫法如下

Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
			->name('password.reset');

Route::post('reset-password', [NewPasswordController::class, 'store'])
			->name('password.update');

NewPasswordController.create() 一樣是用來生成畫面

public function create(Request $request)
{
	return view('auth.reset-password', ['request' => $request]);
}

使用者在畫面輸入內容之後,就透過 NewPasswordController.store() 接收

use Illuminate\Validation\Rules;

public function store(Request $request)
{
	$request->validate([
		'token' => ['required'],
		'email' => ['required', 'email'],
		'password' => ['required', 'confirmed', Rules\Password::defaults()],
	]);

	// Here we will attempt to reset the user's password. If it is successful we
	// will update the password on an actual user model and persist it to the
	// database. Otherwise we will parse the error and return the response.
	$status = Password::reset(
		$request->only('email', 'password', 'password_confirmation', 'token'),
		function ($user) use ($request) {
			$user->forceFill([
				'password' => Hash::make($request->password),
				'remember_token' => Str::random(60),
			])->save();

			event(new PasswordReset($user));
		}
	);

	// If the password was successfully reset, we will redirect the user back to
	// the application's home authenticated view. If there is an error we can
	// redirect them back to where they came from with their error message.
	return $status == Password::PASSWORD_RESET
				? redirect()->route('login')->with('status', __($status))
				: back()->withInput($request->only('email'))
						->withErrors(['email' => __($status)]);
}

收到重設內容的檢查,以及儲存方式,都蠻清晰易懂。即使一時看不出來邏輯,Laravel 也很好心的寫了大量的註解,說明每個段落做的事是什麼。相信各位能很快理解這段的內容。

(p.s. 所以不要再說什麼寫得好的程式不需要註解了。Laravel 的作者群也是會好好寫註解的。)

看完所有的程式之後,我們來看看畫面!

首先是登入時,點擊「忘記密碼」

forgot password

點擊之後,可以在 mailhog 後台看到寄件內容

forgot email

點擊信件之後,會到重設密碼頁面

reset password

到這邊,我們的重設密碼功能,就大功告成囉!

今天的功能我們就介紹到這邊!我們明天見!


上一篇
Day 19:使用寄信功能實作用戶認證信件
下一篇
Day 21:Laravel 9 的密碼處理:Hash::make()
系列文
Laravel 9 漫遊,享受魔法般的極速網頁開發體驗30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言